﻿/*
 * eID Applet Project.
 * Copyright (C) 2009 FedICT.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version
 * 3.0 as published by the Free Software Foundation.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, see 
 * http://www.gnu.org/licenses/.
 */

using System;
using System.Web;
using System.Web.SessionState;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;


namespace Codinfinity.BeIDApplet
{

    public class SignCertAppletService : IHttpHandler, IRequiresSessionState
    {

        public const string IDENTIFIER_SESSION_KEY = "Identifier";

        public const string CHALLENGE_SESSION_KEY = "Challenge";

        public SignCertAppletService()
        {
        }

        public void ProcessRequest(HttpContext httpContext)
        {
            HttpRequest httpRequest = httpContext.Request;
            HttpResponse httpResponse = httpContext.Response;
            if ("GET".Equals(httpRequest.HttpMethod))
            {
                httpResponse.Write("<html><body>The eID Applet Service should not be invoked directly.</body></html>");
                return;
            }
            if (!"POST".Equals(httpRequest.HttpMethod))
            {
                httpResponse.StatusCode = 400; // bad request
                return;
            }
            String protocolVersion = httpRequest.Headers["X-AppletProtocol-Version"];
            if (!"1".Equals(protocolVersion))
            {
                httpResponse.StatusCode = 400; // bad request
                return;
            }
            String messageType = httpRequest.Headers["X-AppletProtocol-Type"];
            if ("HelloMessage".Equals(messageType))
            {
                if (SessionManager.ClientSession.KeyExist(IDENTIFIER_SESSION_KEY))
                    SessionManager.ClientSession.Remove(IDENTIFIER_SESSION_KEY);
                httpResponse.AddHeader("X-AppletProtocol-IncludeIdentity", "true");
                httpResponse.AddHeader("X-AppletProtocol-IncludeAddress", "true");
                httpResponse.AddHeader("X-AppletProtocol-IncludePhoto", "true");
                sendCommand("SignCertificatesRequestMessage", httpResponse);
                /*
                Random random = new Random();
                byte[] challenge = new byte[20];
                random.NextBytes(challenge);
                httpResponse.BinaryWrite(challenge);
                
                SessionManager.ClientSession.Add(CHALLENGE_SESSION_KEY, challenge);
                 */ 
                return;
            }
            else if ("SignCertificatesDataMessage".Equals(messageType))
            {
                try
                {
                    // parse arguments
                    /*
                    foreach (string s in httpRequest.Headers.Keys)
                        Log("Header : " + s + " = " + httpRequest.Headers[s]);
                    */
                    String signCertFileSizeString = httpRequest.Headers["X-AppletProtocol-SignCertFileSize"];
                    String caCertFileSizeString = httpRequest.Headers["X-AppletProtocol-CaCertFileSize"];
                    String rootCaCertFileSizeString = httpRequest.Headers["X-AppletProtocol-RootCaCertFileSize"];
                    String identityFileSizeString = httpRequest.Headers["X-AppletProtocol-IdentityFileSize"];
                    String addressFileSizeString = httpRequest.Headers["X-AppletProtocol-AddressFileSize"];
                    String photoFileSizeString = httpRequest.Headers["X-AppletProtocol-PhotoFileSize"];

                    int signCertFileSize = Int32.Parse(signCertFileSizeString);
                    int caCertFileSize = Int32.Parse(caCertFileSizeString);
                    int rootCaCertFileSize = Int32.Parse(rootCaCertFileSizeString);
                    int identityFileSize = Int32.Parse(identityFileSizeString);
                    int addressFileSize = Int32.Parse(addressFileSizeString);
                    int photoFileSize = Int32.Parse(photoFileSizeString);


                    Stream inputStream = httpRequest.InputStream;

                    byte[] signCertData = new byte[signCertFileSize];
                    inputStream.Read(signCertData, 0, signCertFileSize);
                    
                    byte[] caCertData = new byte[caCertFileSize];
                    inputStream.Read(caCertData, 0, caCertFileSize);

                    byte[] rootCaCertData = new byte[rootCaCertFileSize];
                    inputStream.Read(rootCaCertData, 0, rootCaCertFileSize);
                    
                    byte[] identityFileData = new byte[identityFileSize];
                    inputStream.Read(identityFileData, 0, identityFileSize);

                    byte[] addressFileData = new byte[addressFileSize];
                    inputStream.Read(addressFileData, 0, addressFileSize);

                    byte[] photoFileData = new byte[photoFileSize];
                    inputStream.Read(photoFileData, 0, photoFileSize);


                    X509Certificate2 signCertificate = new X509Certificate2(signCertData);
                    X509Certificate2 caCertificate = new X509Certificate2(caCertData);
                    X509Certificate2 rootCaCertificate = new X509Certificate2(rootCaCertData);
                    

                    //data =salt + legalnotice + chalenge

                    // TODO: verify certificate validity
                    Log("Is certificate valid : "+signCertificate.Verify());
                    var chain = new X509Chain();
                    chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot;
                    chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
                    chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 1, 0);
                    chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
                    bool chainIsValid = chain.Build(signCertificate);
                    Log("Is chain valid : " + chainIsValid);
                    foreach (var s in chain.ChainStatus)
                        Log("Chain status : "+s.Status.ToString()+ "("+s.StatusInformation+")");



                    Identity.AuthnCertificate = signCertificate;
                    ParseIdentity(identityFileData);
                    ParseAddress(addressFileData);

                    System.IO.StreamWriter wr = new StreamWriter(httpContext.Server.MapPath("~/certificate.cer"));
                    wr.Write(Convert.ToBase64String(signCertData));
                    wr.Close();

                    Identity.Identified = Identity.NationalNumber != "";
                    sendCommand("FinishedMessage", httpResponse);
                    return;
                }
                catch (Exception ex)
                {
                    Log("Error :" + ex.Message);
                    Log(ex.Source);
                    Log(ex.StackTrace);
                    Log(ex.TargetSite.ToString());
                    httpResponse.StatusCode = 400; // bad request
                    return;
                }
            }
            else
            {
                httpResponse.StatusCode = 400; // bad request
                return;
            }
            
        }

        private bool ValidateCertificate(X509Certificate2 certificate,X509Certificate2 caCertificate)
        {

            return certificate.Verify();
        }

        private void ParsePhoto(byte[] photoFile)
        {
            Identity.Photo = photoFile;
            return;
        }

        private void ParseAddress(byte[] addressFile)
        {
            int addressFileSize = addressFile.Length;
            int idx = 0;
            while (idx < addressFileSize - 1)
            {
                int tag = addressFile[idx++];
                int length = addressFile[idx++];
                byte[] buffer = new byte[length];
                Array.Copy(addressFile, idx, buffer, 0, length);
                idx += length;
                switch (tag)
                {
                    case 1:
                        Identity.Address = Encoding.UTF8.GetString(buffer);
                        break;
                    case 2:
                        Identity.PostalCode = Encoding.UTF8.GetString(buffer);
                        break;
                    case 3:
                        Identity.Locality = Encoding.UTF8.GetString(buffer);
                        break;
                }
            }
        }

        private void ParseIdentity(byte[] identityFile)
        {
            int identityFileSize = identityFile.Length;
            int idx = 0;

            while (idx < identityFileSize - 1)
            {
                int tag = identityFile[idx++];
                int length = identityFile[idx++];
                byte[] buffer = new byte[length];
                Array.Copy(identityFile, idx, buffer, 0, length);
                idx += length;
                switch (tag)
                {
                    case 5:
                        Identity.Locality = Encoding.UTF8.GetString(buffer);
                        break;
                    case 6:
                        Identity.NationalNumber = Encoding.UTF8.GetString(buffer);
                        break;
                    case 7:
                        Identity.LastName = Encoding.UTF8.GetString(buffer);
                        break;
                    case 8:
                        Identity.FirstName = Encoding.UTF8.GetString(buffer);
                        break;
                    case 10:
                        Identity.Nationality = Encoding.UTF8.GetString(buffer);
                        break;
                    case 11:
                        Identity.PlaceOfBirth = Encoding.UTF8.GetString(buffer);
                        break;
                    case 12:
                        Identity.DateOfBirth = Encoding.UTF8.GetString(buffer);
                        break;
                    case 13:
                        Identity.Gender = Encoding.UTF8.GetString(buffer);
                        break;
                }
            }
        }

        public bool IsReusable
        {
            get
            {
                return true;
            }
        }


        private void sendCommand(string command, HttpResponse httpResponse)
        {
            httpResponse.AddHeader("X-AppletProtocol-Version", "1");
            httpResponse.AddHeader("X-AppletProtocol-Type", command);
        }

        private void Log(string text)
        { 
            System.IO.StreamWriter writer=new StreamWriter(System.Web.HttpContext.Current.Server.MapPath("~/Logs/EidAppletAuthService.txt"),true);
            writer.WriteLine("[" + DateTime.Now.ToLongTimeString() + "] " + text);
            writer.Close();
        }
    }
}
